(function (a) {
    if (typeof define === "function" && define.amd) {
        define(a)
    } else {
        window.driver = a()
    }
})(function () {
    Array.prototype.contains = function (elem) {
        var s = String.fromCharCode(2);
        var r = new RegExp(s + elem + s);
        return (r.test(s + this.join(s) + s))
    };
    if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function (elem) {
            for (var i = 0; i < this.length; i++) {
                if (i in this && this[i] === elem) {
                    return i
                }
            }
            return -1
        }
    }
    Date.parseDate = function (dateStr) {
        var date = (0, eval)("new Date(" + dateStr.replace(/\d+(?=-[^-]+$)/, function (a) {
                return parseInt(a, 10) - 1
            }).match(/\d+/g) + ")");
        date.setMonth(date.getMonth());
        return date
    };
    Date.format = function (date, pattern) {
        pattern = pattern || "yyyy-MM-dd HH:mm:ss SSS";
        return (date || new Date()).format(pattern)
    };
    Date.prototype.format = function (fmt) {
        var o = {
            "M+": this.getMonth() + 1,
            "d+": this.getDate(),
            "h+": this.getHours() % 12 == 0 ? 12 : this.getHours() % 12,
            "H+": this.getHours(),
            "m+": this.getMinutes(),
            "s+": this.getSeconds(),
            "q+": Math.floor((this.getMonth() + 3) / 3),
            S: this.getMilliseconds()
        };
        var week = {
            "0": "/u65e5",
            "1": "/u4e00",
            "2": "/u4e8c",
            "3": "/u4e09",
            "4": "/u56db",
            "5": "/u4e94",
            "6": "/u516d"
        };
        if (/(y+)/.test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length))
        }
        if (/(E+)/.test(fmt)) {
            fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "/u661f/u671f" : "/u5468") : "") + week[this.getDay() + ""])
        }
        for (var k in o) {
            if (new RegExp("(" + k + ")").test(fmt)) {
                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
            }
        }
        return fmt
    };
    var utils = {
        isFunction: function (obj) {
            return Object.prototype.toString.call(obj) === "[object Function]"
        }, isUndefined: function (obj) {
            return Object.prototype.toString.call(obj) === "[object Undefined]"
        }, isNotUndefined: function (obj) {
            return !this.isUndefined(obj)
        }, isNull: function (obj) {
            return obj == null
        }, isNotNull: function (obj) {
            return !this.isNull(obj)
        }, isBlank: function (obj) {
            return this.isNull(obj) || this.isUndefined(obj) || obj == ""
        }, isEmpty: function (obj) {
            if (this.isObject(obj)) {
                for (var p in obj) {
                    if (this.isFunction(obj[p])) {
                        continue
                    }
                    return true
                }
            } else {
                if (this.isArray(obj) || this.isString(obj)) {
                    return obj.length == 0
                }
            }
            return false
        }, isArray: function (obj) {
            return Object.prototype.toString.call(obj) === "[object Array]"
        }, isString: function (obj) {
            return Object.prototype.toString.call(obj) === "[object String]"
        }, isObject: function (obj) {
            return Object.prototype.toString.call(obj) === "[object Object]"
        }, isArguments: function (obj) {
            return Object.prototype.toString.call(obj) === "[object Arguments]"
        }, nowMilliseconds: function () {
            return new Date().getTime()
        }
    };

    function extend(src, dist) {
        if (dist) {
            for (var prop in dist) {
                if (utils.isObject(dist[prop]) && src[prop]) {
                    src[prop] = extend(src[prop], dist[prop])
                } else {
                    src[prop] = dist[prop]
                }
            }
        }
        return src
    }

    window.TRANSACTION_MODES = {
        READONLY: "readonly",
        READ_WRITE: "readwrite",
        READ_WRITE_FLUSH: "readwriteflush ",
        VERION_CHANGE: "verionchange"
    };
    window.CURSOR_TYPES = {
        NEXT: IDBCursor.NEXT,
        NEXT_NO_DUPLICATE: IDBCursor.NEXT_NO_DUPLICATE,
        PREV: IDBCursor.PREV,
        PREV_NO_DUPLICATE: IDBCursor.PREV_NO_DUPLICATE
    };
    window.KEY_RANGE = {
        ltEq: function (x) {
            return IDBKeyRange.upperBound(x)
        }, lt: function (x) {
            return IDBKeyRange.upperBound(x, true)
        }, gtEq: function (x) {
            return IDBKeyRange.lowerBound(x)
        }, gt: function (x) {
            return IDBKeyRange.lowerBound(x, true)
        }, between: function (x, y) {
            return IDBKeyRange.bound(x, y)
        }, gtLt: function (x, y) {
            return IDBKeyRange.bound(x, y, true, true)
        }, gtLtEq: function (x, y) {
            return IDBKeyRange.bound(x, y, true, false)
        }, gtEqLt: function (x, y) {
            return IDBKeyRange.bound(x, y, false, false)
        }, eq: function (x) {
            return IDBKeyRange.only(x)
        }
    };
    function Driver() {
        var driver = {}, win = (typeof window != "undefined" ? window : global);
        window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
        window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
        window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
        driver.database = null;
        driver.open = function (options) {
            var defaultOptions = {
                name: "", version: 1, tables: [], success: function () {
                }, callback: function () {
                }, close: function () {
                }, change: function () {
                }, error: function () {
                }, abort: function () {
                }
            };
            options = utils.isString(options) ? {name: options} : options;
            if (arguments[1]) {
                options.version = arguments[1]
            }
            options = extend(defaultOptions, options || {});
            driver.options = options;
            var dbRequest = indexedDB.open(options.name, options.version);
            dbRequest.onerror = function (e) {
                var error = e.target.error;
                console.log(error.message);
                options.error.call(driver, error);
                options.callback.call(driver, error)
            };
            dbRequest.onsuccess = function (e) {
                driver.database = e.target.result;
                driver.database.onerror = function (e) {
                    var error = e.target.error;
                    options.error.call(driver, error)
                };
                driver.database.onabort = function (e) {
                    options.abort.call(driver, e.target.error)
                };
                options.success.call(driver, driver.database);
                options.callback.call(driver, driver.database)
            };
            dbRequest.onclose = function (e) {
                options.close.call(driver, e.target.result);
                options.callback.call(driver, e.target.result)
            };
            dbRequest.onupgradeneeded = function (e) {
                driver.database = e.target.result;
                for (var j = 0; j < options.tables.length; j++) {
                    driver.createTable(options.tables[j])
                }
                options.change.call(driver, driver.database);
                options.callback.call(driver, driver.database);
                console.warn("DB version changed to " + driver.database.version)
            }
        };
        driver.close = function () {
            if (this.database) {
                this.database.close();
                this.database = null
            }
        };
        driver.transaction = function (options) {
            var defaultOptions = {
                tables: [], mode: TRANSACTION_MODES.READONLY, complete: function () {
                }, error: function () {
                }, callback: function () {
                }
            };
            if (utils.isString(options) || utils.isArray(options)) {
                options = {tables: options}
            }
            if (arguments.length > 1) {
                options.mode = arguments[1]
            }
            options = extend(defaultOptions, options);
            var transaction = this.database.transaction(options.tables, options.mode);
            transaction.oncomplete = function (e) {
                options.complete.call(driver, e)
            };
            transaction.onerror = function (e) {
                var error = e.target.error;
                console.error(e.target.source.name + " " + error.message);
                options.error.call(driver, error)
            };
            options.callback.call(this);
            return transaction
        };
        driver.next = function () {
            return {name: this.database.name, version: this.database.version + 1}
        };
        driver.reOpen = function (options) {
            if (!options || !options.version) {
                options = options || {};
                var next = this.next();
                options.name = next.name;
                options.version = next.version
            }
            this.close();
            this.open(options)
        };
        driver.dropDatabase = function (database) {
            indexedDB.deleteDatabase(database)
        };
        driver.createTable = function (table, isChange, success) {
            table = utils.isString(table) ? {name: table} : table;
            if (isChange) {
                this.reOpen({
                    tables: utils.isArray(table) ? table : [table], success: success || function () {
                    }
                });
                return
            }
            var tables = utils.isArray(table) ? table : [table];
            for (var j = 0; j < tables.length; j++) {
                var table = tables[j];
                if (this.hasTable(table.name)) {
                    continue
                }
                var objectStore = this.database.createObjectStore(table.name, {
                    keyPath: table.key || "id",
                    autoIncrement: !!table.autoIncrement
                });
                if (!utils.isUndefined(table.indexes)) {
                    for (var i = 0; i < table.indexes.length; i++) {
                        var index = table.indexes[i];
                        objectStore.createIndex(index.name, index.column, {unique: !!index.unique});
                        if (table.datas) {
                            for (var k = 0; k < table.datas.length; k++) {
                                objectStore.add(table.datas[k])
                            }
                        }
                    }
                }
            }
        };
        driver.dropTable = function (name) {
            this.reOpen({
                change: function () {
                    this.database.deleteObjectStore(name)
                }
            })
        };
        driver.tables = function () {
            return this.database.objectStoreNames
        };
        driver.hasTable = function (tableName) {
            return this.tables() && this.tables().contains(tableName)
        };
        driver.dropIndex = function (tableName, indexName) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            objectStore.deleteIndex(indexName)
        };
        driver.createIndex = function (tableName, index) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName, TRANSACTION_MODES.READ_WRITE);
            index = utils.isArray(index) ? index : [index];
            for (var i = 0; i < index.length; i++) {
                objectStore.createIndex(index[i].name, index[i].column, {unique: !!index[i].unique})
            }
        };
        driver.insert = function (tableName, datas) {
            var transaction = this.transaction(tableName, TRANSACTION_MODES.READ_WRITE);
            var objectStore = transaction.objectStore(tableName);
            datas = utils.isArray(datas) ? datas : [datas];
            for (var i = 0; i < datas.length; i++) {
                objectStore.add(datas[i])
            }
        };
        driver.update = function (tableName, newVal) {
            var transaction = this.transaction(tableName, TRANSACTION_MODES.READ_WRITE);
            var objectStore = transaction.objectStore(tableName);
            newVal = utils.isArray(newVal) ? newVal : [newVal];
            for (var i = 0; i < newVal.length; i++) {
                objectStore.put(newVal[i])
            }
        };
        driver.pop = function (tableName, callback) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var request = objectStore.openCursor(null, CURSOR_TYPES.NEXT);
            request.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor) {
                    if (callback) {
                        callback.call(driver, cursor.key, cursor.value)
                    }
                    cursor["continue"]();
                    objectStore["delete"](cursor.key)
                }
            };
            request.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.shift = function (tableName, callback) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var request = objectStore.openCursor(CURSOR_TYPES.PREV);
            request.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor) {
                    if (callback) {
                        callback.call(driver, cursor.key, cursor.value)
                    }
                    cursor["continue"]();
                    objectStore["delete"](cursor.key)
                }
            };
            request.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.query = function (tableName, callback, query, count) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var request = objectStore.getAll(query, count);
            request.onsuccess = function (e) {
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            };
            request.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.queryByKey = function (tableName, key, callback) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var request = objectStore.get(key);
            request.onsuccess = function (e) {
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            };
            request.onerror = function (e) {
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.queryAll = function (tableName, callback) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var request = objectStore.getAll();
            request.onsuccess = function (e) {
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            };
            request.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.fetch = function (tableName, callback, keyRange, cursorType) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var request = keyRange ? objectStore.openCursor() : objectStore.openCursor(keyRange, cursorType || CURSOR_TYPES.NEXT);
            request.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor) {
                    if (callback) {
                        callback.call(driver, cursor.key, cursor.value)
                    }
                    cursor["continue"]()
                }
            };
            request.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.index = function (tableName, indexName, key, callback) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var index = objectStore.index(indexName);
            var request = index.get(key);
            request.onsuccess = function (e) {
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            };
            request.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.multiple = function (tableName, indexName, callback, keyRange, cursorType) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var index = objectStore.index(indexName);
            var cursor = index.openCursor(keyRange, cursorType || CURSOR_TYPES.NEXT);
            cursor.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor) {
                    callback.call(driver, cursor.key, cursor.value);
                    cursor["continue"]()
                }
            };
            cursor.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.getAllKeys = function (tableName, callback, query, count) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var request = query ? objectStore.getAllKeys(query, count) : objectStore.getAllKeys();
            request.onsuccess = function (e) {
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            };
            request.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        driver.deleteRow = function (tableName, key) {
            var transaction = this.transaction(tableName, TRANSACTION_MODES.READ_WRITE);
            var objectStore = transaction.objectStore(tableName);
            objectStore["delete"](key)
        };
        driver.clear = function (tableName) {
            var transaction = this.transaction(tableName, TRANSACTION_MODES.READ_WRITE);
            var objectStore = transaction.objectStore(tableName);
            objectStore.clear()
        };
        driver.count = function (tableName, callback, keyRange) {
            var transaction = this.transaction(tableName);
            var objectStore = transaction.objectStore(tableName);
            var countRequest = keyRange ? objectStore.count(keyRange) : objectStore.count();
            countRequest.onsuccess = function (e) {
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            };
            countRequest.onerror = function (e) {
                console.error(e.target.error);
                if (callback) {
                    callback.call(driver, e.target.result)
                }
            }
        };
        return driver
    }

    var logs = [];

    function Log() {
    }

    var sysDriver = new Driver();

    function DriverProxy() {
    }

    DriverProxy.prototype.init = function () {
        if (!sysDriver.database) {
            sysDriver.open({
                name: "sys_db",
                tables: [{name: "sys_params", key: "key"}, {
                    name: "sys_logs",
                    key: "id",
                    autoIncrement: true,
                    indexes: [{name: "logIndex", column: "timestamp", unique: true}]
                }],
                success: function () {
                    setInterval(function () {
                        while (logs.length > 0) {
                            sysDriver.insert("sys_logs", logs.shift())
                        }
                    }, 60 * 1000)
                }
            })
        }
    };
    DriverProxy.prototype.getProxy = function () {
        if (!this.proxy) {
            this.proxy = new Driver()
        }
        return this.proxy
    };
    DriverProxy.prototype.invoke = function (method) {
        if (this.disable) {
            console.warn("Your browser unsuport this operate!");
            return
        }
        var args = Array.prototype.slice.call(arguments).slice(1);
        if (this.debug) {
            console.debug("execute method:[" + method + "], arguments:" + JSON.stringify(args))
        }
        this.before(method, args);
        var result = this[method] ? this[method].apply(this, args) : this.getProxy()[method].apply(this.getProxy(), args);
        this.after(method, args);
        return result
    };
    DriverProxy.prototype.open = function (options) {
        sysDriver.queryByKey("sys_params", "using_db", function (result) {
            if (result) {
                options.name = result.value;
                options.version = Math.max(options.version, result.version)
            }
            this.getProxy().open(options);
            sysDriver.update("sys_params", {key: "using_db", value: options.name, version: options.version})
        })
    };
    DriverProxy.prototype.before = function () {
    };
    DriverProxy.prototype.after = function () {
    };
    var proxy = new DriverProxy();
    window.addEventListener("unload", function () {
        driver.close()
    });
    try {
        proxy.version = "1.0";
        proxy.init();
        proxy.disable = false;
        proxy.enable = !proxy.disable
    } catch (e) {
        proxy.disable = proxy.enable;
        proxy.enable = !proxy.disable;
        console.error(e);
        return
    }
    return {
        debug: function (enable) {
            proxy.debug = enable != false
        }, open: function (options) {
            proxy.invoke("open", options)
        }, dropDatabase: function (database) {
            proxy.invoke("dropDatabase", database)
        }, createTable: function (table, isChange, success) {
            proxy.invoke("createTable", table, isChange, success)
        }, dropTable: function (name) {
            proxy.invoke("dropTable", name)
        }, tables: function () {
            return proxy.invoke("tables")
        }, hasTable: function (tableName) {
            return proxy.invoke("hasTable", tableName)
        }, dropIndex: function (tableName, indexName) {
            proxy.invoke("dropIndex", tableName, indexName)
        }, createIndex: function (tableName, index) {
            proxy.invoke("createIndex", tableName, index)
        }, insert: function (tableName, datas) {
            proxy.invoke("insert", tableName, datas)
        }, update: function (tableName, newVal) {
            proxy.invoke("update", tableName, newVal)
        }, pop: function (tableName, callback) {
            proxy.invoke("pop", tableName, callback)
        }, shift: function (tableName, callback) {
            proxy.invoke("shift", tableName, callback)
        }, query: function (tableName, callback, query, count) {
            proxy.invoke("query", tableName, callback, query, count)
        }, queryByKey: function (tableName, key, callback) {
            proxy.invoke("queryByKey", tableName, key, callback)
        }, queryAll: function (tableName, callback) {
            proxy.invoke("queryAll", tableName, callback)
        }, fetch: function (tableName, callback, keyRange, cursorType) {
            proxy.invoke("fetch", tableName, callback, keyRange, cursorType)
        }, index: function (tableName, indexName, key, callback) {
            proxy.invoke("index", tableName, callback, key, callback)
        }, multiple: function (tableName, indexName, callback, keyRange, cursorType) {
            proxy.invoke("multiple", tableName, indexName, callback, keyRange, cursorType)
        }, getAllKeys: function (tableName, callback, query, count) {
            proxy.invoke("getAllKeys", tableName, callback, query, count)
        }, deleteRow: function (tableName, key) {
            proxy.invoke("deleteRow", tableName, key)
        }, clear: function (tableName) {
            proxy.invoke("clear", tableName)
        }, count: function (tableName, callback, keyRange) {
            proxy.invoke("count", tableName, callback, keyRange)
        }
    }
});